import * as fs from 'fs'
import mock from 'mock-fs'
import {assert, expect} from 'chai'
import * as util from '../src/util'

import { sevenZipPack } from '../src/util';
import { PackInputs } from '../src/pack-inputs';
import * as step from '@flow-step/step-toolkit'
import * as sinon from 'sinon';
describe('emptyDir', () => {
    beforeEach(() => {
        mock({
            'emptyDir': {},
            'dirWithFiles': {
                'file1.txt': 'content1',
                'file2.txt': 'content2'
            },
            'dirWithSubdirs': {
                'subdir1': {},
                'subdir2': {}
            },
            'mixedDir': {
                'file1.txt': 'content1',
                'subdir1': {}
            },
            'nonExistentDir': {},
            'permissionDeniedDir': {
                'file1.txt': mock.file({ content: 'content1', mode: 0o000 })
            }
        });
    });

    afterEach(() => {
        mock.restore();
    });

    it('emptyDir should handle an empty directory without errors', () => {
        util.emptyDir('emptyDir');
        assert(fs.readdirSync('emptyDir').length==0);
    });

    it('emptyDir should delete all files in a directory', () => {
        util.emptyDir('dirWithFiles');
        assert(fs.readdirSync('dirWithFiles').length==0);
    });

    it('emptyDir should recursively empty all subdirectories', () => {
        util.emptyDir('dirWithSubdirs');
        assert(fs.readdirSync('dirWithSubdirs').length==2);
    });

    it('emptyDir should handle a directory with mixed content', () => {
        util.emptyDir('mixedDir');
        let strings = fs.readdirSync('mixedDir')
        assert(strings.length==1);
    });

    it('emptyDir should handle a non-existent directory without errors', () => {
        expect(() => util.emptyDir('nonExistentDir')).not.throw;
    });


});

describe('rmEmptyDir', () => {
    beforeEach(() => {
        mock({
            '/tmp1': {
                'emptyDir': {},
                'nonEmptyDir': {
                    'file.txt': 'content'
                },
                'nestedEmptyDir': {
                    'subDir': {}
                },
                'nestedNonEmptyDir': {
                    'subDir': {
                        'file.txt': 'content'
                    }
                },
                'topLevelEmptyDir': {}
            }
        });
    });

    afterEach(() => {
        mock.restore();
    });




    it('should not remove the top-level directory if it is empty', () => {
        util.rmEmptyDir('/tmp1/topLevelEmptyDir', 0);
        expect(fs.existsSync('/tmp1/topLevelEmptyDir')).equal(true);
    });
});




describe('sevenZipPack', () => {
    let execStub: sinon.SinonStub;


    beforeEach(() => {

        mock({
            '/root1': {
                'source': 'file content',
            },
        });
        execStub = sinon.stub(step.exec, 'call');
    });

    afterEach(() => {
        execStub.restore();
        mock.restore();
    });

    it('should set includePathInArtifact to true if filePath contains space', async () => {
        const inputs  = {
            filePath: 'path with space',
            artifactFullName: 'artifact.tar',
            sourcePath: ['/root1/source'],
            includePathInArtifact: 'false',
            sevenZipLocation: '7z.exe',
            projectDir: 'project'
        } as PackInputs;

        await sevenZipPack(inputs);

        expect(inputs.includePathInArtifact).to.equal('true');
        expect(execStub.calledWith('7z.exe', ['a', '-ttar', 'artifact.tar', '/root1/source'], sinon.match.any)).to.be.true;
    });

    it('should execute correct command if filePath does not contain space and includePathInArtifact is true', async () => {
        const inputs = {
            filePath: 'path',
            artifactFullName: 'artifact.tar',
            sourcePath: ['/root1/source'],
            includePathInArtifact: 'true',
            sevenZipLocation: '7z.exe',
            projectDir: 'project'
        } as PackInputs;

        await sevenZipPack(inputs);

        expect(execStub.calledWith('7z.exe', ['a', '-ttar', 'artifact.tar', '/root1/source'], sinon.match.any)).to.be.true;
    });

    it('should execute correct command if filePath does not contain space, includePathInArtifact is false, and sourcePath is a file', async () => {
        const inputs ={
            filePath: 'path',
            artifactFullName: 'artifact.tar',
            sourcePath: ['/root1/source'],
            includePathInArtifact: 'false',
            sevenZipLocation: '7z.exe',
            projectDir: 'project'
        } as PackInputs;


        await sevenZipPack(inputs);

        expect(execStub.calledWith('7z.exe', ['a', '-ttar', 'artifact.tar', '/root1/source'], sinon.match.any)).to.be.true;
    });

    it('should execute correct command if filePath does not contain space, includePathInArtifact is false, and sourcePath is a directory', async () => {
        const inputs = {
            filePath: 'path',
            artifactFullName: 'artifact.tar',
            sourcePath: ['/root1'],
            includePathInArtifact: 'false',
            sevenZipLocation: '7z.exe',
            projectDir: 'project'
        } as PackInputs;


        await sevenZipPack(inputs);

        expect(execStub.calledWith('7z.exe', ['a', '-ttar', 'artifact.tar', '/root1/*'], sinon.match.any)).to.be.true;
    });

    it('should throw error if filePath does not contain space, includePathInArtifact is false, and sourcePath is neither file nor directory', async () => {
        const inputs= {
            filePath: 'path',
            artifactFullName: 'artifact.tar',
            sourcePath: ['source'],
            includePathInArtifact: 'false',
            sevenZipLocation: '7z.exe',
            projectDir: 'project'
        } as PackInputs;


        try {
            await sevenZipPack(inputs);
            expect.fail('Expected an error to be thrown');

        } catch (error) {
            // @ts-ignore
            expect(error.message).to.contain('no such file or directory');
        }
    });
});





describe('tarPack', () => {
    let stepInfoStub: sinon.SinonStub;
    let stepErrorStub: sinon.SinonStub;
    let stepExecCallStub: sinon.SinonStub;
    let execStub: sinon.SinonStub;

    beforeEach(() => {
        mock({
            '/path/to': {
                'file': 'file content',
                'file.txt': 'file content',

            },
            '/path/to/dir': {
                'file': 'file content',
                'file.txt': 'file content',

            },
        });
        stepExecCallStub = sinon.stub(step.exec, 'call');
        stepInfoStub = sinon.stub(step, 'info');
        stepErrorStub = sinon.stub(step, 'error');
    });

    afterEach(() => {
        sinon.restore();
        mock.restore();
    });

    it('should handle gitIgnore option correctly', async () => {
        const packInputs = {
            gitIgnore: 'true',
            filePath: '/path/to/file',
            artifactFullName: 'artifact.tgz',
            sourcePath: ['/path/to/file'],
            includePathInArtifact: 'false',
            projectDir: '/path/to/project'
        } as PackInputs;

        await util.tarPack(packInputs);

        expect(stepExecCallStub.calledWith(
            'tar',
            ['--exclude=.git', '-zcPf', 'artifact.tgz', '-C', '/path/to','file'],
            sinon.match.any
        )).to.be.true;
    });

    it('should handle filePath with space correctly', async () => {
        const packInputs = {
            gitIgnore: 'false',
            filePath: '/path to/file',
            artifactFullName: 'artifact.tgz',
            sourcePath: ['/path to/file'],
            includePathInArtifact: 'false',
            projectDir: '/path/to/project'
        } as PackInputs;

        await util.tarPack(packInputs);

        expect(packInputs.includePathInArtifact).to.equal('true');
        expect(stepExecCallStub.calledWith(
            'tar',
            ['','-zcPf', 'artifact.tgz', '/path to/file'],
            sinon.match.any
        )).to.be.true;
    });

    it('should handle includePathInArtifact option correctly', async () => {
        const packInputs = {
            gitIgnore: 'false',
            filePath: '/path/to/file',
            artifactFullName: 'artifact.tgz',
            sourcePath: ['/path/to/file'],
            includePathInArtifact: 'true',
            projectDir: '/path/to/project'
        } as PackInputs;

        await util.tarPack(packInputs);

        expect(stepExecCallStub.calledWith(
            'tar',
            ['','-zcPf', 'artifact.tgz', '/path/to/file'],
            sinon.match.any
        )).to.be.true;
    });

    it('should handle file packing without path', async () => {

        const packInputs = {
            gitIgnore: 'false',
            filePath: '/path/to/file',
            artifactFullName: 'artifact.tgz',
            sourcePath: ['/path/to/file.txt'],
            includePathInArtifact: 'false',
            projectDir: '/path/to/project'
        } as PackInputs;

        await util.tarPack(packInputs);

        expect(stepExecCallStub.calledWith(
            'tar',
            ['','-zcPf', 'artifact.tgz', '-C', '/path/to', 'file.txt'],
            sinon.match.any
        )).to.be.true;
    });

    it('should handle directory packing', async () => {

        const packInputs = {
            gitIgnore: 'false',
            filePath: '/path/to/dir',
            artifactFullName: 'artifact.tgz',
            sourcePath: ['/path/to/dir'],
            includePathInArtifact: 'false',
            projectDir: '/path/to/project'
        } as PackInputs;

        await util.tarPack(packInputs);

        expect(stepExecCallStub.calledWith(
            'tar',
            ['','-zcPf', 'artifact.tgz', '-C', '/path/to/dir', '.'],
            sinon.match.any
        )).to.be.true;
    });

    it('should throw error when file or directory not found', async () => {

        const packInputs = {
            gitIgnore: 'false',
            filePath: '/path/to/nonexistent',
            artifactFullName: 'artifact.tgz',
            sourcePath: ['/path/to/nonexistent'],
            includePathInArtifact: 'false',
            projectDir: '/path/to/project'
        } as PackInputs;

        try {
            await util.tarPack(packInputs);
            expect.fail('Expected an error to be thrown');
        } catch (error) {
            // @ts-ignore
            expect(error.message).to.contain('no such file or directory');
        }
    });
});
